package web

import (
	"gitee.com/chicken-c/go-learn/webook/internal/domain"
	"gitee.com/chicken-c/go-learn/webook/internal/repostiory"
	"gitee.com/chicken-c/go-learn/webook/internal/service"
	ijwt "gitee.com/chicken-c/go-learn/webook/internal/web/jwt"
	"gitee.com/chicken-c/go-learn/webook/pkg/logger"
	regexp "github.com/dlclark/regexp2"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/redis/go-redis/v9"
	"net/http"
	"time"
)

type UserHandler struct {
	srv           service.UserService
	codeSrv       service.CodeService
	emailRegex    *regexp.Regexp
	passwordRegex *regexp.Regexp
	Client        redis.Cmdable
	l             logger.Logger
	//jwt相关
	ijwt.Handler
}

func NewUserHandler(srv service.UserService, codeSrv service.CodeService, Client redis.Cmdable, l logger.Logger, jwtHdl ijwt.Handler) *UserHandler {
	const (
		emailRegexPattern = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
		// 和 " 比起来，用 ` 看起来就比较清爽
		passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[.$@$!%*#?&])[A-Za-z\d.$@$!%*#?&]{8,72}$`
	)

	//返回一个UserHandler对象的地址
	return &UserHandler{
		srv:           srv,
		codeSrv:       codeSrv,
		emailRegex:    regexp.MustCompile(emailRegexPattern, 0),
		passwordRegex: regexp.MustCompile(passwordRegexPattern, 0),
		Client:        Client,
		l:             l,
		Handler:       jwtHdl,
	}
}

// 统一的路由注册接口
func (u *UserHandler) RegisterRoutes(server *gin.Engine) {
	server.POST("/users/signup", u.SignUp)
	server.POST("/users/login", u.Login)
	server.POST("/users/logout", u.Logout)
	server.POST("/users/edit", u.Edit)
	server.GET("/users/profile", u.Profile)
	server.GET("/refresh_token", u.RefreshToken)

	//手机号登录
	server.POST("/users/login_sms/code/send", u.SendSMSLoginCode)
	server.POST("/users/login_sms", u.LoginSMS)
}

// 登录、注册、修改、查询四个路由处理方法
func (u *UserHandler) SignUp(ctx *gin.Context) {
	type SignUpReq struct {
		Email           string `json:"email"`
		Password        string `json:"password"`
		ConfirmPassword string `json:"confirmpassword"`
	}

	//解析http请求
	var req SignUpReq
	err := ctx.Bind(&req)
	if err != nil {
		//Bind会自动发送一个错误响应，我们直接return
		return
	}

	isMatch, err := u.emailRegex.MatchString(req.Email)
	if err != nil {
		ctx.String(http.StatusOK, "内部错误")
		return
	}
	if !isMatch {
		ctx.String(http.StatusOK, "邮箱格式不正确")
		return
	}
	//判断密码有效性
	isMatch, err = u.passwordRegex.MatchString(req.Password)
	if err != nil {
		ctx.String(http.StatusOK, "内部错误")
		return
	}
	if !isMatch {
		ctx.String(http.StatusOK, "密码格式不正确")
		return
	}
	//判断密码是否一致
	if req.ConfirmPassword != req.Password {
		ctx.String(http.StatusOK, "两次密码不一致")
		u.l.Debug("两次密码不一致", logger.Field{
			Key: "passwd",
			Val: req.ConfirmPassword,
		})
		return
	}

	//数据库操作
	err = u.srv.SignUp(ctx, domain.User{
		Email:    req.Email,
		Password: req.Password,
	})
	if err == service.ErrUserDuplicate {
		ctx.String(http.StatusOK, "邮件重复")
		return
	}
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}

	ctx.String(http.StatusOK, "注册成功")

}
func (u *UserHandler) Login(ctx *gin.Context) {
	type LoginReq struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}

	var req LoginReq
	err := ctx.Bind(&req)
	if err != nil {
		return
	}

	//查询数据库
	var user domain.User
	user, err = u.srv.Login(ctx, req.Email, req.Password)
	if err == service.ErrInvalidUserOrPassword {
		ctx.String(http.StatusOK, "邮箱或密码错误")
		return
	}
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}

	err = u.SetLoginToken(ctx, user.Id)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	ctx.String(http.StatusOK, "登录成功")
}

func (u *UserHandler) Edit(ctx *gin.Context) {
	type EditReq struct {
		Nickname string `json:"nickname"`
		AboutMe  string `json:"aboutMe"`
		Birthday string `json:"birthday"`
	}

	req := &EditReq{}
	err := ctx.Bind(req)
	if err != nil {
		return
	}

	//格式的校验
	if req.Nickname == "" {
		ctx.String(http.StatusOK, "昵称不能为空")
		return
	}

	//YYYY-MM-DD
	birthday, err := time.Parse(time.DateOnly, req.Birthday)
	if err != nil {
		ctx.String(http.StatusOK, "日期格式不对")
		return
	}

	//其实这个地方不需要判断，因为我们数据库表设置了varchar(1024),如果过长是会返回Error的
	if len(req.AboutMe) > 1024 {
		ctx.String(http.StatusOK, "aboutMe 字段不能超过1024个字符")
		return
	}

	//获取userId
	UClaims := ctx.MustGet("user")
	user, _ := UClaims.(ijwt.UserClaims)

	//数据库操作
	err = u.srv.Edit(ctx, domain.User{
		Id:       user.Id,
		Nickname: req.Nickname,
		AboutMe:  req.AboutMe,
		Birthday: birthday,
	})

	if err != nil {
		//系统错误
		ctx.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	ctx.String(http.StatusOK, "信息修改成功")
}

func (u *UserHandler) Logout(ctx *gin.Context) {
	err := u.CleanToken(ctx)
	if err != nil {
		ctx.String(http.StatusOK, "退出登录失败")
		return
	}
	ctx.String(http.StatusOK, "退出登录成功")
}

func (u *UserHandler) Profile(ctx *gin.Context) {
	UClaims := ctx.MustGet("user")
	user, _ := UClaims.(ijwt.UserClaims)

	retUser, err := u.srv.Profile(ctx, user.Id)
	if err == service.ErrUserNotFound {
		//用户不存在
		ctx.String(http.StatusOK, "用户不存在")
		return
	}
	if err != nil {
		//系统错误
		ctx.AbortWithStatus(http.StatusInternalServerError)
		return
	}

	ctx.JSON(http.StatusOK, gin.H{"email": retUser.Email, "nickname": retUser.Nickname, "aboutMe": retUser.AboutMe})
}

func (u *UserHandler) RefreshToken(ctx *gin.Context) {
	//提取长token
	tokenStr := u.ExtractToken(ctx)

	claims := ijwt.RefreshClaims{}
	token, err := jwt.ParseWithClaims(tokenStr, &claims, func(tk *jwt.Token) (interface{}, error) {
		return []byte(ijwt.AtKey), nil
	})
	if err != nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	if token == nil || !token.Valid {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	//查看token是否已经无效
	ok, err := u.CheckSession(ctx, claims.Ssid)
	//这里是true的话，代表token就失效了
	if ok || err != nil {
		//这里有可能redis崩了，需要考虑降级策略
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	//设置新的短token
	err = u.SetJWTToken(ctx, claims.Id, claims.Ssid)
	if err != nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	ctx.String(http.StatusOK, "刷新成功")
}

// 手机号登录路由处理
func (u *UserHandler) SendSMSLoginCode(ctx *gin.Context) {
	type sendSMSReq struct {
		Phone string `json:"phone"`
	}

	var req sendSMSReq
	err := ctx.Bind(&req)
	if err != nil {
		return
	}

	if req.Phone == "" {
		ctx.String(http.StatusOK, "手机号不能为空")
		return
	}

	var biz = "login"
	err = u.codeSrv.Send(ctx, biz, req.Phone)
	if err == repostiory.ErrCodeSendTooMany {
		ctx.String(http.StatusOK, "验证码发送太频繁")
		return
	}
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}

	ctx.String(http.StatusOK, "验证码发送成功")

}
func (u *UserHandler) LoginSMS(ctx *gin.Context) {
	type LoginReq struct {
		Code  string `json:"code"`
		Phone string `json:"phone"`
	}

	var req LoginReq
	err := ctx.Bind(&req)
	if err != nil {
		return
	}

	//这里我们不需要校验手机号和验证码，因为redis会帮我们返回错误-系统错误
	var biz = "login"
	ok, err := u.codeSrv.Verify(ctx, biz, req.Phone, req.Code)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	if !ok {
		ctx.String(http.StatusOK, "验证码错误")
		return
	}

	//验证码正确，设置JWT,就需要找到User信息,且如果没有注册过，我们就要注册一个用户。
	ue, err := u.srv.FindOrCreate(ctx, req.Phone)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}

	//设置JWT
	err = u.SetLoginToken(ctx, ue.Id)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	ctx.String(http.StatusOK, "登录成功")
}
